using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;

namespace PrisonStep
{
    /// <summary>
    /// This class describes our player in the game. 
    /// </summary>
    public class Player : BoundingCylinder
    {
        #region Fields

        private enum States { Start, StanceStart, Stance, WalkStart, WalkLoopStart, WalkLoop, TurnLoopStart, TurnLoop,
                            LowerBazooka, RaiseBazooka, Aiming, Aimed, UnAim, WalkLoopBazooka, WalkStartBazooka,
                            StartCrouch, CrouchingPlay, CrouchPause, CrouchStandUp
        }

        /// <summary>
        /// current state
        /// </summary>
        private States state = States.Start;

        private KeyboardState previousKeyboard;
        SpriteBatch spriteBatch;

        private MouseState previousMouse;


        /// <summary>
        /// Game that uses this player
        /// </summary>
        private PrisonGame game;

        /// <summary>
        /// The player orientation as a simple angle
        /// </summary>
        private float orientation = 1.6f;
        private float standingHeight = 174;
        private float crouchHeight = 119f;
        private Vector2 aimPosition = new Vector2(0, 0);

        /// <summary>
        /// The player transformation matrix. Places the player where they need to be.
        /// </summary>
        private Matrix transform;

        private string oldRegion = "R_Section1";
        private bool slimeHit = false;
        private SpriteFont scoreFont;
        private int gameScore = 0;

        private SpitFire spitFire;
        public SpitFire SpitFire { get { return spitFire; } set { spitFire = value; } }

        private bool dalekPressed = false;
        public bool DalekPressed { get { return dalekPressed; } }

        /// <summary>
        /// The player model
        /// </summary>
        private AnimatedModel victoria;

        /// <summary>
        /// The Pie Bazooka that Victoria carries
        /// </summary>
        private PieBazooka pieBazooka;

        /// <summary>
        /// All of the pies that are currently in the world
        /// </summary>
        private PieFire pieFire;

        /// <summary>
        /// Alien
        /// </summary>
        private Alien alien;
        private Gort gort;
        private Dalek dalek;

        private bool inDoor = false;

        /// <summary>
        /// Contains the Phibes Model information
        /// </summary>
        private PhibesModel phibesModel;

        public PhibesModel PhibesModel { get { return phibesModel; } }
        public bool InDoor { get { return inDoor; } }
        public bool SlimeHit { get { return slimeHit; } set { slimeHit = value; } }
        public int GameScore { get { return gameScore; } set { gameScore = value; } }

        #endregion


        public Player(PrisonGame game, PhibesModel phibesModel)
        {
            location = new Vector3(425, 0, 1053);
            radius = 10f;
            height = standingHeight;

            this.game = game;
            this.phibesModel = phibesModel;

            victoria = new AnimatedModel(game, "Victoria");
            victoria.AddAssetClip("dance", "Victoria-dance");
            victoria.AddAssetClip("stance", "Victoria-stance");
            victoria.AddAssetClip("walk", "Victoria-walk");
            victoria.AddAssetClip("walkstart", "Victoria-walkstart");
            victoria.AddAssetClip("walkloop", "Victoria-walkloop");
            victoria.AddAssetClip("leftturn", "Victoria-leftturn");
            victoria.AddAssetClip("rightturn", "Victoria-rightturn");
            victoria.AddAssetClip("raisebazooka", "Victoria-raisebazooka");
            victoria.AddAssetClip("crouchbazooka", "Victoria-crouchbazooka");
            victoria.AddAssetClip("lowerbazooka", "Victoria-lowerbazooka");
            victoria.AddAssetClip("walkloopbazooka", "Victoria-walkloopbazooka");
            victoria.AddAssetClip("walkstartbazooka", "Victoria-walkstartbazooka");
            SetPlayerTransform();

            pieBazooka = new PieBazooka(game);
            pieFire = new PieFire(game, phibesModel);
            spitFire = new SpitFire(phibesModel, game);
            gort = new Gort(game, phibesModel);
            alien = new Alien(game, phibesModel);
            dalek = new Dalek(game, phibesModel);
            previousKeyboard = Keyboard.GetState();
            previousMouse = Mouse.GetState();
        }

        /// <summary>
        /// Set the value of transform to match the current location
        /// and orientation.
        /// </summary>
        private void SetPlayerTransform()
        {
            transform = Matrix.CreateRotationY(orientation);
            transform.Translation = location;     
        }


        public void LoadContent(ContentManager content)
        {
            phibesModel.LoadContent(content);
            victoria.LoadContent(content);
            pieBazooka.LoadContent(content);
            pieFire.LoadContent(content);
            spitFire.LoadContent(content);
            gort.LoadContent(content);
            alien.LoadContent(content);
            dalek.LoadContent(content);

            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(game.GraphicsDevice);
            scoreFont = content.Load<SpriteFont>("scorefont");
        }

        /// <summary>
        /// Handles the animation and input while in the Stance State
        /// It return if the time remaining (delta if the state has changed, 0 other wise)
        /// </summary>
        /// <param name="keyboardState"></param>
        /// <param name="delta"></param>
        public float Stance(KeyboardState keyboardState, double delta )
        {
            //just wait for something to happen
            if (keyboardState.IsKeyDown(Keys.Left) || keyboardState.IsKeyDown(Keys.Right) )
            {
                state = States.TurnLoopStart;
                return (float)delta;
            }

            if (keyboardState.IsKeyDown(Keys.Up) || keyboardState.IsKeyDown(Keys.Down))
            {
                state = States.WalkStart;
                return (float)delta;
            }

            if (keyboardState.IsKeyDown(Keys.A))
            {
                state = States.RaiseBazooka;
                return (float)delta;
            }

            if (keyboardState.IsKeyDown(Keys.C))
            {
                state = States.StartCrouch;
                return (float)delta;
            }

            victoria.Update(delta);

            return 0;
        }

        
        /// <summary>
        /// Handles the animation and input while in the WalkLoop State, which is 
        /// the state that handles forward animation between animation restarts.
        /// It returns the time left over, if any, after updating the animation
        /// </summary>
        /// <param name="keyboardState"></param>
        /// <param name="delta"></param>
        /// <returns></returns>
        float WalkLoop(KeyboardState keyboardState, double delta)
        {
            //all keys up, stop walking
            if (keyboardState.IsKeyUp(Keys.Left) && keyboardState.IsKeyUp(Keys.Right) &&
                    keyboardState.IsKeyUp(Keys.Up) && keyboardState.IsKeyUp(Keys.Down))
            {
                state = States.StanceStart;
                return 0;
            }

            //update orientation
            if (keyboardState.IsKeyDown(Keys.Left))
            {
                orientation += (float)(delta);
            }
            if (keyboardState.IsKeyDown(Keys.Right))
            {
                orientation -= (float)(delta);
            }

            //if animation ended, restart it, and return how far into the next animation we should be
            float timeLeft = victoria.Update(delta);
            if (timeLeft > 0)
            {
                state = States.WalkLoopStart;
            }
            return timeLeft;
        }

        /// <summary>
        /// Handles the animation and input while in the TurnLoop State, which is 
        /// the state that handles turning animation between animation restarts.
        /// It returns the time left over, if any, after updating the animation
        /// </summary>
        /// <param name="keyboardState"></param>
        /// <param name="delta"></param>
        /// <returns></returns>
        float TurnLoop(KeyboardState keyboardState, double delta)
        {
            //all keys up, stop walking
            if (keyboardState.IsKeyUp(Keys.Left) && keyboardState.IsKeyUp(Keys.Right) &&
                    keyboardState.IsKeyUp(Keys.Up))
            {
                state = States.StanceStart;
                return 0;
            }

            //switch to forward motion
            if (keyboardState.IsKeyDown(Keys.Up))
            {
                state = States.StanceStart;
                return (float)delta;
            }

            //if animation ended, restart it, and return how far into the next animation we should be
            float timeLeft = victoria.Update(delta);
            if (timeLeft > 0)
            {
                state = States.TurnLoopStart;
            }
            return timeLeft;
        }

        public void Update(GameTime gameTime, bool isOpen)
        {
            double deltaTotal = gameTime.ElapsedGameTime.TotalSeconds;
            KeyboardState keyboardState = Keyboard.GetState();
            MouseState currentMouse = Mouse.GetState();
            float timeLeft = 0;

            if ((previousMouse.LeftButton == ButtonState.Released) &&
                (currentMouse.LeftButton == ButtonState.Pressed))
            {
                Vector3 mouseV = new Vector3(currentMouse.X, currentMouse.Y, 1);
                Vector3 spaceV = game.Graphics.GraphicsDevice.Viewport.Unproject(mouseV, game.Camera.Projection, game.Camera.View, Matrix.Identity);
                Vector3 rayO = game.Camera.Eye;
                Vector3 rayD = spaceV - rayO;
                rayD.Normalize();
                dalekPressed = dalek.testCollisionRay(rayO, rayD, 0);
                //System.Diagnostics.Trace.WriteLine(removeLater.ToString());
            }


            if (dalekPressed)
            {
                dalek.Y += 1;
            }

            if (!dalekPressed && dalek.Y > 0)
            {
                dalek.Y -= 1;

            }
            /*
            if ((previousMouse.LeftButton == ButtonState.Pressed) &&
                (currentMouse.LeftButton == ButtonState.Pressed) && dalekPressed)
            {
                dalek.Y += 1;
                //dalekPressed = false;
                //dalek.Y -= 1;
            }
             * */

                /*
            else if (!dalekPressed && dalek.Y > 0)
            {
                dalek.Y -= 1;

            }
                 * */


            //loop while there is time left for the animations
            do
            {
                double delta = deltaTotal;

                #region State Machine
                switch (state)
                {
                    //entry point only
                    case States.Start:
                        state = States.StanceStart;
                        delta = 0;
                        break;

                    //start up the stance animation and go immediatly to the next state
                    case States.StanceStart:
                        victoria.PlayClip("walkstartbazooka");
                        victoria.Update(0);
                        victoria.Speed = 0;
                        state = States.Stance;
                        delta = 0;
                        break;

                    //wait for something to happen (a return value greater than 0 means something happened)
                    case States.Stance:
                        if (0 < Stance(keyboardState, delta))
                            delta = 0;
                        break;

                    //start up the walk animation and go immediatly to the next state
                    case States.WalkStart:
                        victoria.PlayClip("walkstartbazooka");
                        victoria.Speed = 2;
                        state = States.WalkLoop;
                        delta = 0;
                        break;

                    //continue playing the walk loop animation until time runs out or there is new input
                    case States.WalkLoop:
                        timeLeft = WalkLoop(keyboardState, delta);
                        break;

                    //start up the walk loop animation and go immediatly to the next state
                    case States.WalkLoopStart:
                        victoria.PlayClip("walkloopbazooka");
                        state = States.WalkLoop;
                        delta = 0;
                        break;

                    case States.Aiming:
                        if (!victoria.Player.FinishedPlaying)
                            victoria.Update(delta);
                        else
                            state = States.Aimed;
                        break;

                    case States.Aimed:
                        int spineBone = victoria.Model.Bones["Bip01 Spine1"].Index;
                        //allow for up to 45 degrees of rotation
                        if (keyboardState.IsKeyDown(Keys.Left) && aimPosition.X < Math.PI / 4)
                        {   
                            aimPosition.X += (float)delta;
                        }
                        if (keyboardState.IsKeyDown(Keys.Right) && aimPosition.X > -Math.PI / 4)
                        {
                            aimPosition.X -= (float)delta;
                        }
                        if (keyboardState.IsKeyDown(Keys.Up) && aimPosition.Y < Math.PI / 4)
                        {
                            aimPosition.Y += (float)delta;
                        }
                        if (keyboardState.IsKeyDown(Keys.Down) && aimPosition.Y > -Math.PI / 4)
                        {
                            aimPosition.Y -= (float)delta;
                        }
                        
                        if (keyboardState.IsKeyDown(Keys.A))
                        {
                            aimPosition = new Vector2(0, 0);
                            victoria.Speed = 2;
                            victoria.PlayClip("lowerbazooka");
                            state = States.LowerBazooka;
                        }

                        victoria.changeModifiedTransforms(spineBone,
                            Matrix.CreateRotationX(aimPosition.X) *
                            Matrix.CreateRotationZ(aimPosition.Y));

                        victoria.Update(delta);
                        break;

                    case States.LowerBazooka:
                        if (!victoria.Player.FinishedPlaying)
                            victoria.Update(delta);
                        else
                            state = States.Stance;
                        break;

                    case States.RaiseBazooka:
                        victoria.Speed = 2;
                        victoria.PlayClip("raisebazooka");
                        state = States.Aiming;
                        break;

                    case States.StartCrouch:
                        victoria.Speed = 2;
                        victoria.PlayClip("crouchbazooka");
                        height = crouchHeight;
                        state = States.CrouchingPlay;
                        break;

                    case States.CrouchingPlay:
                        //pause the clip after 1 second
                        if (victoria.Player.Time < 1.0)
                        {
                            victoria.Update(delta);
                        }
                        else
                        {
                            victoria.Speed = 0;
                            state = States.CrouchPause;
                        }
                        break;

                    case States.CrouchPause:
                        if (keyboardState.IsKeyDown(Keys.C))
                        {
                            state = States.CrouchStandUp;
                            victoria.Speed = 1;
                        }

                        break;

                    case States.CrouchStandUp:
                        if (victoria.Player.FinishedPlaying)
                        {
                            height = standingHeight;
                            state = States.Stance;
                        }
                        else
                        {
                            victoria.Update(delta);
                        }

                        break;

                    //start up the turn animation and go immediatly to the next state
                    case States.TurnLoopStart:
                        if (keyboardState.IsKeyDown(Keys.Right))
                        {
                            victoria.PlayClip("rightturn");
                            victoria.Speed = 2;
                        }
                        else
                        {
                            victoria.PlayClip("leftturn");
                            victoria.Speed = 2;
                        }
                        state = States.TurnLoop;
                        delta = 0;
                        break;

                    //continue playing the turn animation until time runs out or there is new input
                    case States.TurnLoop:
                        timeLeft = TurnLoop(keyboardState, delta);
                        if (timeLeft != 0)
                            state = States.TurnLoopStart;
                        break;
                }
                #endregion

                //if timeLeft has something in it and its not bigger than delta 
                //(which happens when the animations are not restarted), 
                //update delta to be the amount of time used 
                if (timeLeft != 0 && timeLeft < delta)
                    delta -= timeLeft;
                //only do location and orientation updates if victoria is in her final position
                //for this screen refresh
                if( delta == deltaTotal )
                {

                    //
                    // Part 1:  Compute a new orientation
                    //

                    Matrix deltaMatrix = victoria.DeltaMatrix;
                    float deltaAngle = (float)Math.Atan2(deltaMatrix.Backward.X, deltaMatrix.Backward.Z);
                    float newOrientation = orientation + deltaAngle;

                    //
                    // Part 2:  Compute a new location
                    //

                    
                    // We are likely rotated from the angle the model expects to be in
                    // Determine that angle.
                    Matrix rootMatrix = victoria.RootMatrix;
                    float actualAngle = (float)Math.Atan2(rootMatrix.Backward.X, rootMatrix.Backward.Z);
                    Vector3 move = Vector3.TransformNormal(victoria.DeltaPosition, Matrix.CreateRotationY(newOrientation - actualAngle));
                    
                    //allow her to go backwards
                    if (keyboardState.IsKeyDown(Keys.Down))
                        move *= -1.0f;

                    Vector3 newLocation = location + move;
                    newLocation.Y = 0;

                    #region Region specific and collision managment
                    string region = phibesModel.TestRegion(newLocation);
                    
                    if (region.StartsWith("R_Door"))
                    {
                        inDoor = true;
                        foreach (PrisonModel pm in game.PhibesModel)
                        {
                            foreach (int door in pm.DoorToBone.Keys)
                            {
                                //if the door region number == the door bone
                                if (Convert.ToInt32(region.Substring(region.Length - 1)) == door)
                                {
                                    bool doorOpen = phibesModel.TestDoor(door, pm, newLocation, (float)delta, location, orientation);
                                    if (doorOpen && state != States.CrouchPause)
                                    {
                                        location = newLocation;
                                    }
                                    else if (region != oldRegion && state != States.CrouchPause)
                                    {
                                        location = newLocation;
                                    }
                                }
                            }
                        }
                    }

                    else if (region != "")
                    {
                        //handle the sliming
                        if (region.StartsWith("R_Section1"))
                        {
                            slimeHit = false;   //cannot be slimed in the control room

                            if (game.Greeness > 0)
                                game.Greeness -= (float)delta;
                            if (game.Greeness < 0)
                                game.Greeness = 0;

                        }
                        if (region.StartsWith("R_Section6") || slimeHit)
                        {
                            if (game.Greeness < 1)
                                game.Greeness += (float)delta;
                            if (game.Greeness > 1)
                                game.Greeness = 1;
                        }
                        if (state != States.CrouchPause)
                            location = newLocation;
                    }

                    if (!region.StartsWith("R_Door"))
                    {
                        inDoor = false;
                    }


                    oldRegion = region;

                    if (!region.StartsWith("R_Door") && !alien.InDoor && !gort.InDoor)
                    {
                        //continue closing all doors if they are in the process of closing
                        foreach (PrisonModel pm in game.PhibesModel)
                        {
                            foreach (int door in pm.DoorToBone.Keys)
                            {
                                pm.DoorOpen[door] = false;
                                pm.closeDoor(door, (float)delta);
                            }
                        }
                    }

                    if (region == "R_Section1")
                    {
                        victoria.Section = 1;
                        foreach (PrisonModel pm in game.PhibesModel)
                        {
                            pm.SlimeActivated = false;
                        }
                    }

                    else if (region == "R_Section6")
                    {
                        victoria.Section = 6;
                        foreach (PrisonModel pm in game.PhibesModel)
                        {
                            pm.SlimeActivated = true;
                        }
                    }
                    else if (region == "R_Section5")
                    {
                        victoria.Section = 5;
                    }
                    else if (region == "R_Section2")
                    {
                        victoria.Section = 2;
                    }
                    else if (region == "R_Section3")
                    {
                        victoria.Section = 3;
                    }
                    else if (region == "R_Section4")
                    {
                        victoria.Section = 4;
                    }

                    #endregion                  

                    orientation = newOrientation;
                    SetPlayerTransform();

                    #region Calculate position for bazooka, update bazooka, and update pies
                    int handbone = victoria.Model.Bones["Bip01 R Hand"].Index;

                    Matrix bazMat =
                        Matrix.CreateRotationX(MathHelper.ToRadians(109.5f)) *
                        Matrix.CreateRotationY(MathHelper.ToRadians(9.7f)) *
                        Matrix.CreateRotationZ(MathHelper.ToRadians(72.9f)) *
                        Matrix.CreateTranslation(-9.6f, 11.85f, 21.1f) *
                        victoria.GetBoneAbsoluteTransform(handbone) *
                        transform;

                    pieBazooka.Transform = bazMat;

                    pieBazooka.Update(gameTime);

                    if (region.StartsWith("R_Section1"))
                    {
                        //reset number of pies to 10
                        if (pieBazooka.TotalPies != 10)
                        {
                            pieBazooka.TotalPies = 10;
                            pieFire.LoadPie();
                        }
                    }

                    //fire pies
                    if (keyboardState.IsKeyDown(Keys.Space) && 
                        !previousKeyboard.IsKeyDown(Keys.Space) && 
                        state == States.Aimed)
                    {
                        if (pieBazooka.TotalPies > 0)
                        {
                            pieFire.Fire();
                            game.Smoke.AddParticles(pieBazooka.Transform.Translation);
                            System.Diagnostics.Trace.WriteLine(game.Smoke.FreeParticleCount);
                            pieBazooka.TotalPies--;
                            if (pieBazooka.TotalPies > 0)
                                pieFire.LoadPie();
                        }
                        
                    }

                    pieFire.Update(gameTime, Matrix.CreateRotationX((float)Math.PI / 2) * pieBazooka.Transform, 
                        isOpen, alien, gort, dalek, this);
                    spitFire.Update(gameTime, this);
                    #endregion

                    
                }

                deltaTotal -= delta;

            } while (deltaTotal > 0);
            gort.Update(gameTime, transform, inDoor, this);
            alien.Update(gameTime, transform, inDoor, this);
            dalek.Update(gameTime, transform, inDoor, this);

            previousKeyboard = keyboardState;

            game.Camera.Center = location - new Vector3(0, -175, 0);
            game.Camera.Eye = Vector3.Transform(new Vector3(0, 200, -350), transform);
            Vector3 snapDir = game.Camera.Center - game.Camera.Eye;
            snapDir.Normalize();

            while (phibesModel.TestRegion(new Vector3(game.Camera.Eye.X, 0, game.Camera.Eye.Z)) == "")
            {
                game.Camera.Eye += snapDir;
            }

            pieBazooka.FourthColor = victoria.FourthColor;
            pieBazooka.FourthLocation = victoria.FourthLocation;
            pieBazooka.FourthIntensity = victoria.FourthIntensity;
        }

        /// <summary>
        /// This function is called to draw the player.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="gameTime"></param>
        public void Draw(GraphicsDeviceManager graphics, GameTime gameTime)
        {


            Matrix transform = Matrix.CreateRotationY(orientation);
            transform.Translation = location;

            victoria.Draw(graphics, gameTime, transform);
            gort.Draw(graphics, gameTime, pieFire);  
            alien.Draw(graphics, gameTime, pieFire);
            dalek.Draw(graphics, gameTime);
            pieBazooka.Draw(graphics, gameTime);
            pieFire.Draw(graphics, gameTime);
            
            //draw score
            spriteBatch.Begin();
            spriteBatch.DrawString(scoreFont, gameScore.ToString(), new Vector2(10, 10), Color.White);
            spriteBatch.End();

            //draw the alien spit last
            spitFire.Draw(graphics, gameTime);
        }

        public void MouseCollision(Vector3 rayStart, Vector3 direction, MouseState mouseState)
        {

            if (alien.testCollisionRay(rayStart, direction, 100.0f))
            {
                alien.Y = mouseState.Y;

            }
        }


    }
}
